package drds.server.config.loader.xml;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.sql.SQLSyntaxErrorException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import drds.server.config.model.ShardRule;
import drds.server.config.model.TableRule;
import drds.server.config.util.ConfigException;
import drds.server.config.util.ConfigUtil;
import drds.server.config.util.ParameterMapping;
import drds.server.route.shard_algorithm.AbstractShardAlgorithm;
import drds.server.util.SplitUtil;

/**
 * 
 */
@SuppressWarnings("unchecked")
public class TableRuleLoader {
	private final static String DEFAULT_DTD = "/rule.dtd";
	private final static String DEFAULT_XML = "/rule.xml";

	private final Map<String, TableRule> tableRuleMap;
	// private final Set<RuleConfig> rules;
	private final Map<String, AbstractShardAlgorithm> shardAlgorithmMap;

	public TableRuleLoader(String tableRuleFilePath) {
		// rule名 -> TableRule
		this.tableRuleMap = new HashMap<String, TableRule>();
		// shardId -> ShardAlgorithm
		this.shardAlgorithmMap = new HashMap<String, AbstractShardAlgorithm>();
		load(DEFAULT_DTD, tableRuleFilePath == null ? DEFAULT_XML : tableRuleFilePath);
	}

	public TableRuleLoader() {
		this(null);
	}

	public Map<String, TableRule> getTableRuleMap() {
		return (Map<String, TableRule>) (tableRuleMap.isEmpty() ? Collections.emptyMap() : tableRuleMap);
	}

	private void load(String dtdFilePath, String tableRuleFilePath) {
		InputStream dtdFileInputStream = null;
		InputStream tableRuleFileInputStream = null;
		try {
			dtdFileInputStream = TableRuleLoader.class.getResourceAsStream(dtdFilePath);
			tableRuleFileInputStream = TableRuleLoader.class.getResourceAsStream(tableRuleFilePath);
			// 读取出语意树
			Element rootElement = ConfigUtil.getDocument(dtdFileInputStream, tableRuleFileInputStream).getDocumentElement();
			// 加载Function
			loadShardAlgorithmMap(rootElement);
			// 加载TableRule
			loadTableRuleMap(rootElement);
		} catch (ConfigException e) {
			throw e;
		} catch (Exception e) {
			throw new ConfigException(e);
		} finally {
			if (dtdFileInputStream != null) {
				try {
					dtdFileInputStream.close();
				} catch (IOException e) {
				}
			}
			if (tableRuleFileInputStream != null) {
				try {
					tableRuleFileInputStream.close();
				} catch (IOException e) {
				}
			}
		}
	}

	/**
	 * function标签结构： <function name="partbymonth"
	 * class="io..route.function.PartitionByMonth"> <property
	 * name="dateFormat">yyyy-MM-dd</property> <property
	 * name="sBeginDate">2015-01-01</property> </function>
	 */
	private void loadShardAlgorithmMap(Element rootElement) throws ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException {
		NodeList shardAlgorithmNodeList = rootElement.getElementsByTagName("shard_algorithm");
		for (int i = 0, length = shardAlgorithmNodeList.getLength(); i < length; ++i) {
			Node shardAlgorithmNode = shardAlgorithmNodeList.item(i);
			if (shardAlgorithmNode instanceof Element) {
				Element element = (Element) shardAlgorithmNode;
				String shardAlgorithmId = element.getAttribute("shard_algorithm_id");
				// 如果Map已有，则function重复
				if (shardAlgorithmMap.containsKey(shardAlgorithmId)) {
					throw new ConfigException("rule function " + shardAlgorithmId + " duplicated!");
				}
				// 获取class标签
				String classFullName = element.getAttribute("class");
				// 根据class利用反射新建分片算法
				AbstractShardAlgorithm shardAlgorithm = createShardAlgorithm(shardAlgorithmId, classFullName);
				// 根据读取参数配置分片算法
				ParameterMapping.mapping(shardAlgorithm, ConfigUtil.loadElements(element));
				// 每个AbstractPartitionAlgorithm可能会实现init来初始化
				shardAlgorithm.init();
				// 放入functions map
				shardAlgorithmMap.put(shardAlgorithmId, shardAlgorithm);
			}
		}
	}

	private AbstractShardAlgorithm createShardAlgorithm(String shardAlgorithmId, String classFullName) throws ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException {
		Class<?> $class = Class.forName(classFullName);
		// 判断是否继承AbstractPartitionAlgorithm
		if (!AbstractShardAlgorithm.class.isAssignableFrom($class)) {
			throw new IllegalArgumentException("rule function must implements " + AbstractShardAlgorithm.class.getName() + ", name=" + shardAlgorithmId);
		}
		return (AbstractShardAlgorithm) $class.newInstance();
	}

	/**
	 * tableRule标签结构： <tableRule name="sharding-by-month"> <rule>
	 * <columns>create_date</columns> <algorithm>partbymonth</algorithm> </rule>
	 * </tableRule>
	 * 
	 * @param rootElement
	 * @throws SQLSyntaxErrorException
	 */
	private void loadTableRuleMap(Element rootElement) throws SQLSyntaxErrorException {
		// 获取每个tableRule标签
		NodeList tableRuleNodeList = rootElement.getElementsByTagName("table_rule");
		for (int i = 0, length = tableRuleNodeList.getLength(); i < length; ++i) {
			Node tableRuleNode = tableRuleNodeList.item(i);
			if (tableRuleNode instanceof Element) {
				Element element = (Element) tableRuleNode;
				// 先判断是否重复
				String name = element.getAttribute("name");
				if (tableRuleMap.containsKey(name)) {
					throw new ConfigException("table rule " + name + " duplicated!");
				}
				// 获取rule标签
				NodeList ruleNodeList = element.getElementsByTagName("rule");
				int $length = ruleNodeList.getLength();
				if ($length > 1) {
					throw new ConfigException("only one rule can defined :" + name);
				}
				// 目前只处理第一个，未来可能有多列复合逻辑需求
				// RuleConfig是保存着rule与function对应关系的对象
				ShardRule shardRule = loadShardRule((Element) ruleNodeList.item(0));
				String shardAlgorithmId = shardRule.getShardAlgorithmId();
				// 判断function是否存在，获取function
				AbstractShardAlgorithm shardAlgorithm = shardAlgorithmMap.get(shardAlgorithmId);
				if (shardAlgorithm == null) {
					throw new ConfigException("can't find function of name :" + shardAlgorithmId);
				}
				shardRule.setShardAlgorithm(shardAlgorithm);
				// 保存到tableRules
				tableRuleMap.put(name, new TableRule(name, shardRule));
			}
		}
	}

	private ShardRule loadShardRule(Element rootElement) throws SQLSyntaxErrorException {
		// 读取columns
		Element columnsElement = ConfigUtil.loadElement(rootElement, "columns");
		String column = columnsElement.getTextContent();
		String[] columns = SplitUtil.split(column, ',', true);
		if (columns.length > 1) {
			throw new ConfigException("table rule coulmns has multi values:" + columnsElement.getTextContent());
		}
		// 读取algorithm
		Element algorithmElement = ConfigUtil.loadElement(rootElement, "algorithm");
		String algorithmText = algorithmElement.getTextContent();
		return new ShardRule(column.toLowerCase(), algorithmText);
	}

}